/**
 *  @file TimeDuration.h
 */
#include "TimeDuration.h"
#include "../common/CppMemPool.h"
#include "../lang/IntCheck.h"
#include "../exceptions/OverflowException.h"

using namespace cppflib::lang;
using namespace cppflib::exceptions;

namespace cppflib
{

namespace util
{

const i64_t TimeDuration::TICKS_PERDAY = I64N(864000000000);
const i64_t TimeDuration::TICKS_PERHOUR = I64N(36000000000);
const i64_t TimeDuration::TICKS_PERMIN = I64N(600000000);
const i64_t TimeDuration::TICKS_PERSEC = I64N(10000000);
const i64_t TimeDuration::TICKS_PERMILLISEC = I64N(10000);

PRIVATE void __ThrowExceptionIfOverflow(arithop_t op, i64_t a, i64_t b)
{
   if (CheckOverflow(op, a, b))
      throw OverflowException(_S("Overflow error! Number of ticks exceed range."), -1);
}

PRIVATE i64_t __ToTicks(int days, int hours, int mins, int secs, int milliSecs)
{
   i64_t ticks = 0;
   i64_t tVal = 0;
   
   if (milliSecs != 0) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(milliSecs), TimeDuration::TICKS_PERMILLISEC);
      tVal = milliSecs * TimeDuration::TICKS_PERMILLISEC;
      __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, tVal);
      ticks += tVal;
   }

   if (secs != 0) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(secs), TimeDuration::TICKS_PERSEC);
      tVal = secs * TimeDuration::TICKS_PERSEC;
      __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, tVal);
      ticks += tVal;
   }

   if (mins != 0) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(mins), TimeDuration::TICKS_PERMIN);
      tVal = mins * TimeDuration::TICKS_PERMIN;
      __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, tVal);
      ticks += tVal;
   }

   if (hours != 0) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(hours), TimeDuration::TICKS_PERHOUR);
      tVal = hours * TimeDuration::TICKS_PERHOUR;
      __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, tVal);
      ticks += tVal;
   }

   if (days != 0) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(days), TimeDuration::TICKS_PERDAY);
      tVal = days * TimeDuration::TICKS_PERDAY;
      __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, tVal);
      ticks += tVal;
   }

   return ticks;
}

void TimeDuration::InitTicks(int days, int hours, int mins, int secs, int milliSecs)
{
   try {
      this->ticks = __ToTicks(days, hours, mins, secs, milliSecs);
   }
   catch(OverflowException &exp) {
      THROW_CTOR_EXCEP_CODE(OverflowException, exp.GetMsg(), exp.GetErrCode()); // throw again
   }
}

TimeDuration::TimeDuration(void)
{
   ticks = 0;
}

TimeDuration::TimeDuration(i64_t ticks)
{
   this->ticks = ticks;
}

TimeDuration::TimeDuration(int hours, int mins, int secs)
{
   InitTicks(0, hours, mins, secs, 0);
}

TimeDuration::TimeDuration(int days, int hours, int mins, int secs)
{
   InitTicks(days, hours, mins, secs, 0);
}

TimeDuration::TimeDuration(int days, int hours, int mins, int secs, int milliSecs)
{
   InitTicks(days, hours, mins, secs, milliSecs);
}

TimeDuration::~TimeDuration(void)
{
   ReleaseBeforeThrowing();
}

void TimeDuration::ReleaseBeforeThrowing()
{
   // nothing to release in TimeDuration
}

TimeDuration& TimeDuration::Add(const TimeDuration &td)
{
   __ThrowExceptionIfOverflow(lang::AOP_ADD, ticks, td.TotalTicks());
   ticks += td.TotalTicks();
   return *this;
}

/**
 *  Negate the value of this time duration
 */
TimeDuration& TimeDuration::Negate()
{
   __ThrowExceptionIfOverflow(lang::AOP_MUL, ticks, -1);
   ticks = -ticks;
   return *this;
}

/**
 *  Convert this time duration to its absolute value
 */
TimeDuration& TimeDuration::MakeAbsolute()
{
   if (IsNeg()) 
      Negate();
   return *this;
}

/**
 *  @return 0 -- same
 *  @return 1 -- this time duration is greater
 *  @return -1 -- this time duration is less
 */
int TimeDuration::Compare(const TimeDuration &td) const
{
   if (ticks > td.TotalTicks()) return 1;
   if (ticks < td.TotalTicks()) return -1;
   return 0;
}

bool TimeDuration::IsNeg() const
{
   return (this->ticks < I64N(0));
}

/**
 *  Return days component
 *
 *  @return can be -ve or +ve
 */
int TimeDuration::Days() const
{
   int ret = TotalDays();
   return IsNeg() ? -ret : ret;
}

/**
 *  Return seconds component
 *
 *  @return between -23 to 23
 */
int TimeDuration::Hours() const
{
   int ret = static_cast<int>(TotalHours() % 24);
   return IsNeg() ? -ret : ret;
}

/**
 *  Return minutes component
 *
 *  @return between -59 to 59
 */
int TimeDuration::Mins() const
{
   int ret = static_cast<int>(TotalMins() % 60);
   return IsNeg() ? -ret : ret;
}

/**
 *  Return seconds component
 *
 *  @return between -59 to 59
 */
int TimeDuration::Seconds() const
{
   int ret = static_cast<int>(TotalSeconds() % 60);
   return IsNeg() ? -ret : ret;
}

/**
 *  Return milliseconds component
 *
 *  @return between -999 to 999
 */
int TimeDuration::MilliSeconds() const
{
   int ret = static_cast<int>(TotalMilliSeconds() % 1000);
   return IsNeg() ? -ret : ret;
}

int TimeDuration::TotalDays() const
{
   u64_t t = TotalAbsoluteTicks();
   return static_cast<int>(t / TICKS_PERDAY);
}

i64_t TimeDuration::TotalHours() const
{
   u64_t t = TotalAbsoluteTicks();
   return t / TICKS_PERHOUR;
}

i64_t TimeDuration::TotalMins() const
{
   u64_t t = TotalAbsoluteTicks();
   return t / TICKS_PERMIN;
}

i64_t TimeDuration::TotalSeconds() const
{
   u64_t t = TotalAbsoluteTicks();
   return t / TICKS_PERSEC;
}

i64_t TimeDuration::TotalMilliSeconds() const
{
   u64_t t = TotalAbsoluteTicks();
   return t / TICKS_PERMILLISEC;
}

i64_t TimeDuration::TotalTicks() const
{
   return ticks;
}

u64_t TimeDuration::TotalAbsoluteTicks() const
{
   if (ticks < I64N(0)) {
      __ThrowExceptionIfOverflow(lang::AOP_MUL, static_cast<i64_t>(-1), ticks);
      return static_cast<u64_t>(-ticks);
   }

   return ticks;
}


_DLLAPI bool operator==(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) == 0);
}

_DLLAPI bool operator!=(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) != 0);
}

_DLLAPI bool operator<(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) < 0);
}

_DLLAPI bool operator>(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) > 0);
}

_DLLAPI bool operator<=(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) <= 0);
}

_DLLAPI bool operator>=(const TimeDuration& td1, const TimeDuration& td2)
{
   return (td1.Compare(td2) >= 0);
}


} // end of namespace util

} // end of namespace cppflib
